/*
 * Copyright (c) 2011, Marc-Andre Ferland - madrang@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the The Warrent Team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * This software is free for non-commercial use. You may not use this
 *       software, in whole or in part, in support of any commercial product
 *       without the express consent of the author.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE WARRENT TEAM BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;
using System.Collections.Generic;

namespace OsHelper.UsbNet
{
	public abstract class UsbDevice
	{
		//private UsbNet.Descriptors.StringDescriptor std_StrDesc;
		private List<int> ls_InterfaceReleaseList;
		
		protected UsbDevice()
		{
			this.ls_InterfaceReleaseList = new List<int>();
			//this.std_StrDesc = new UsbNet.Descriptors.StringDescriptor(new byte[255]);
		}
		
		#region Method
		
		public abstract void Open();
		
		public virtual void Close()
		{
			if(this.IsOpen) {
				//Use to array as ReleaseInterface remove item from ls_InterfaceReleaseList.
				foreach (int item in this.ls_InterfaceReleaseList.ToArray()) {
					this.ReleaseInterface(item);
				}
			}
		}
		
		public virtual void Dispose()
		{
			this.Close();
		}
		
		/// <summary>
		/// Sends a usb device reset command.
		/// </summary>
		/// <remarks>
		/// <para>
		/// The system will attempt to restore the previous configuration and
		/// alternate settings after the reset has completed.
		/// </para>
		/// <para>
		/// If the reset fails, the descriptors change,
		/// or the previous state cannot be restored,
		/// the device will appear to be disconnected and reconnected.
		/// This means that the device handle is no longer valid,
		/// you should close it and rediscover the device.
		/// The method will throw <see cref="Error.DeviceNotFoundException"/> when this is the case.
		/// </para>
		/// <para>
		/// This is a blocking function which usually incurs a noticeable delay.
		/// </para>
		/// </remarks>
		public abstract void ResetDevice ();
		
		public abstract int Transfer(UsbNet.Descriptors.EndpointDescriptor Endpoint, byte[] Data, int Length, int Timeout);
		
		public abstract int ControlTransfer(RequestAttributes MyAttributes, byte RequestType, short Value, short Index, byte[] Data, short Length, int Timeout);
		
		public override string ToString ()
		{
			return string.Format("Address: {0}.{1}", this.BusNumber, this.DeviceAddress);
		}
		
		protected void CheckOpen(string Msg)
		{
			if(!this.IsOpen)
				throw new System.InvalidOperationException(Msg);
		}
		
		#endregion
		
		#region Interface
		
		/// <summary>
		/// Sets an alternate interface for the specified interface.
		/// </summary>
		public virtual void SetAlternateInterface (int InterfaceID, byte AltInterfaceID)
		{
			this.CheckOpen("Device is not open.");
			
			RequestAttributes ReqAttributes = new RequestAttributes();
			ReqAttributes.Direction = TransferDirection.DeviceToHost;
			ReqAttributes.Type = Type.Standard;
			ReqAttributes.Recipient = Recipient.Interface;
			
			this.ControlTransfer(ReqAttributes,
			                     (byte)StandardRequestsType.SetInterface,
			                     (short)AltInterfaceID,
			                     (short)InterfaceID,
			                     null,
			                     0,
			                     1000);
		}

		/// <summary>
		/// Gets the selected alternate interface of the specified interface.
		/// </summary>
		public virtual byte GetAlternateInterface (byte InterfaceID)
		{
			this.CheckOpen("Device is not open.");
			
			RequestAttributes ReqAttributes = new RequestAttributes();
			ReqAttributes.Direction = TransferDirection.DeviceToHost;
			ReqAttributes.Type = Type.Standard;
			ReqAttributes.Recipient = Recipient.Interface;
			
			byte[] ResultData = new byte[1];
			
			this.ControlTransfer(ReqAttributes,
			                     (byte)StandardRequestsType.GetInterface,
			                     0,
			                     (short)InterfaceID,
			                     ResultData,
			                     1,
			                     1000);
			
			return ResultData[0];
		}
		
		/// <summary>
		/// Claims the specified interface of the device.
		/// </summary>
		public virtual void ClaimInterface (int InterfaceID)
		{
			this.ls_InterfaceReleaseList.Add(InterfaceID);
		}

		/// <summary>
		/// Releases an interface that was previously claimed with <see cref="ClaimInterface"/>.
		/// </summary>
		public virtual void ReleaseInterface (int InterfaceID)
		{
			if(this.ls_InterfaceReleaseList.Contains(InterfaceID))
				this.ls_InterfaceReleaseList.Remove(InterfaceID);
		}
		
		#endregion
		
		#region Configuration

		public Descriptors.ConfigurationDescriptor GetConfiguration(byte Index)
		{
			RequestAttributes ReqAttributes = new RequestAttributes();
			ReqAttributes.Direction = TransferDirection.DeviceToHost;
			
			Descriptors.DescriptorType DescType = new Descriptors.DescriptorType(Descriptors.StandardDescriptorSubType.Configuration);
			
			byte[] TempData = new byte[Descriptors.ConfigurationDescriptor.Size];
			
			this.ControlTransfer(ReqAttributes,
			                     (byte)StandardRequestsType.GetDescriptor,
			                     (short) ((((byte)DescType) << 8) | Index),
			                     (short)0,
			                     TempData,
			                     (short)TempData.Length,
			                     1000);
			
			Descriptors.ConfigurationDescriptor ConfDesc;
			ConfDesc = Descriptors.ConfigurationDescriptor.FromBytes (TempData);
			TempData = new byte[ConfDesc.TotalLength];
			
			this.ControlTransfer(ReqAttributes,
			                     (byte)StandardRequestsType.GetDescriptor,
			                     (short) ((((byte)DescType) << 8) | Index),
			                     (short)0,
			                     TempData,
			                     (short)TempData.Length,
			                     1000);
			
			return Descriptors.ConfigurationDescriptor.FromBytes(TempData);
		}
		
		public Descriptors.ConfigurationDescriptor GetConfigurationByValue(int ConfigurationValue)
		{
			byte NumbConf = this.DeviceDescriptor.ConfigurationCount;
			
			for (byte i = 0; i < NumbConf; i++) {
				Descriptors.ConfigurationDescriptor Conf = this.GetConfiguration(i);
				
				if(Conf.ConfigurationValue == ConfigurationValue)
					return Conf;
			}
			throw new KeyNotFoundException("The specified Configuration value cannot be found.");
		}
		
		public byte ConfigurationIndexByValue(int ConfigurationValue)
		{
			byte NumbConf = this.DeviceDescriptor.ConfigurationCount;
			
			for (byte i = 0; i < NumbConf; i++) {
				Descriptors.ConfigurationDescriptor Conf = this.GetConfiguration(i);
				
				if(Conf.ConfigurationValue == ConfigurationValue)
					return i;
			}
			throw new KeyNotFoundException("The specified Configuration value cannot be found.");
		}
		
		public virtual UsbNet.Descriptors.ConfigurationDescriptor ActiveConfiguration {
			get {
				return this.GetConfigurationByValue(this.Configuration);
			}
			set {
				this.Configuration = value.ConfigurationValue;
			}
		}
		
		/// <summary>
		/// The active configuration value.
		/// A zero value means the device is not configured and a non-zero value indicates the device is configured.
		/// </summary>
		public virtual byte Configuration {
			get {
				this.CheckOpen("Can't receive data while device is not open.");
				
				byte[] Data = new byte[1];
				
				RequestAttributes ReqAttribute = new RequestAttributes();
				ReqAttribute.Direction = TransferDirection.DeviceToHost;
				
				this.ControlTransfer(ReqAttribute,
				                     (byte)StandardRequestsType.GetConfiguration,
				                     0, 0, Data, (short)Data.Length, 1000);
				return Data[0];
			}
			set {
				this.CheckOpen("Can't send data while device is not open.");
				
				RequestAttributes ReqAttribute = new RequestAttributes();
				ReqAttribute.Direction = TransferDirection.HostToDevice;
				
				this.ControlTransfer(ReqAttribute,
				                     (byte)StandardRequestsType.SetConfiguration,
				                     value, 0, null, 0, 1000);
			}
		}
		
		public virtual IEnumerable<UsbNet.Descriptors.ConfigurationDescriptor> ConfigurationList ()
		{
			byte MaxCount = this.DeviceDescriptor.ConfigurationCount;
			
			for (byte Index = 0; Index < MaxCount; Index++) {
				yield return this.GetConfiguration(Index);
			}
		}
		
		public bool IsConfigured {
			get {
				return (this.Configuration > 0);
			}
		}
		
		#endregion
		
		#region String
		
		public virtual string GetString(byte Index, LanguageIdentifiers Lang)
		{
			if(Index <= 0 || !this.SupportString)
				return string.Empty;
			
			this.CheckOpen("Device is not open.");
			
			RequestAttributes ReqAttributes = new RequestAttributes();
			ReqAttributes.Direction = TransferDirection.DeviceToHost;
			ReqAttributes.Type = Type.Standard;
			ReqAttributes.Recipient = Recipient.Device;
			
			Descriptors.DescriptorType DescType = new Descriptors.DescriptorType(Descriptors.StandardDescriptorSubType.String);
			
			byte[] Data = new byte[255];
			
			this.ControlTransfer(ReqAttributes,
			                     (byte)StandardRequestsType.GetDescriptor,
			                     (short) ((((byte)DescType) << 8) | Index),
			                     (short)Lang,
			                     Data,
			                     (short)Data.Length,
			                     1000);
			
			return Descriptors.StringDescriptor.FromBytes(Data).Value;
		}
		
		public string GetString(byte Index)
		{
			return this.GetString(Index, this.Language);
		}
		
		private Descriptors.LanguageIdentifiersDescriptor GetLanguageDescriptor()
		{
			/*
			 * String index zero for all languages returns a string
			 * descriptor that contains an array of two-byte LANGID codes supported by the device. Table 9-15 shows the
			 * LANGID code array. A USB device may omit all string descriptors. USB devices that omit all string
			 * descriptors must not return an array of LANGID codes.
			 * */
			
			RequestAttributes ReqAttributes = new RequestAttributes();
			ReqAttributes.Direction = TransferDirection.DeviceToHost;
			
			Descriptors.DescriptorType DescType = new Descriptors.DescriptorType(Descriptors.StandardDescriptorSubType.String);
			
			byte[] TmpData = new byte[255];
			
			this.ControlTransfer(ReqAttributes,
			                     (byte)StandardRequestsType.GetDescriptor,
			                     (short) ((((byte)DescType) << 8) | 0),
			                     (short)0,
			                     TmpData,
			                     (short)TmpData.Length,
			                     1000);
			
			return UsbNet.Descriptors.LanguageIdentifiersDescriptor.FromBytes(TmpData);
		}
		
		private Descriptors.LanguageIdentifiersDescriptor CachedLanguageDescriptor;
		public virtual Descriptors.LanguageIdentifiersDescriptor LanguageDescriptor {
			get {
				if(this.CachedLanguageDescriptor == null) {
					if(!this.IsOpen)
						return new Descriptors.LanguageIdentifiersDescriptor();
					
					try {
						this.CachedLanguageDescriptor = this.GetLanguageDescriptor();
						
						foreach (LanguageIdentifiers LangItem in this.LanguageDescriptor.LanguageIdentifiers) {
							this.Language = LangItem;
							break;
						}
					} catch (PipeErrorException) {
						this.CachedLanguageDescriptor = new Descriptors.LanguageIdentifiersDescriptor();
					}
				}
				return this.CachedLanguageDescriptor;
			}
		}
		
		public bool SupportString {
			get {
				return (this.LanguageDescriptor.LanguageIdentifiers.Count > 0);
			}
		}
		
		#endregion
		
		#region DeviceDescriptor
		
		private Descriptors.DeviceDescriptor GetDeviceDescriptor()
		{
			this.CheckOpen("Device is not open.");
			
			RequestAttributes ReqAttributes = new RequestAttributes();
			ReqAttributes.Direction = TransferDirection.DeviceToHost;
			
			Descriptors.DescriptorType DescType = new Descriptors.DescriptorType(Descriptors.StandardDescriptorSubType.Device);
			
			byte[] TmpData = new byte[255];
			
			this.ControlTransfer(ReqAttributes,
			                     (byte)StandardRequestsType.GetDescriptor,
			                     (short) ((((byte)DescType) << 8) | 0),
			                     (short)0,
			                     TmpData,
			                     (short)TmpData.Length,
			                     1000);
			
			return UsbNet.Descriptors.DeviceDescriptor.FromBytes(TmpData);
		}
		
		private Descriptors.DeviceDescriptor CachedDeviceDescriptor;
		public virtual Descriptors.DeviceDescriptor DeviceDescriptor {
			get {
				if(this.CachedDeviceDescriptor == null)
					this.CachedDeviceDescriptor = this.GetDeviceDescriptor();
				
				return this.CachedDeviceDescriptor;
			}
		}
		
		#endregion
		
		#region Properties
		
		public abstract byte BusNumber { get; }
		
		public abstract byte DeviceAddress { get; }
		
		public abstract bool IsOpen { get; }
		
		public LanguageIdentifiers Language { get; set; }
		
		#endregion
		
		#region Class
		
		/*public class Interface : IDisposable
		{
			public UsbDevice BaseDevice { get; private set; }
			public int ID { get; private set; }
			
			public Interface (UsbDevice MyDevice, int InterfaceID)
			{
				this.BaseDevice = MyDevice;
				this.ID = InterfaceID;
				
				this.BaseDevice.ClaimInterface(this.ID);
				
			}
		}
		
		public class Endpoint : IDisposable
		{
			public Endpoint (UsbDevice MyDevice)
			{
			}
		}*/
		
		#endregion
		
		#region Static

		/// <summary>
		/// Gets a list of all available USB devices.
		/// </summary>
		public static List<UsbDevice> AllDevices {
			get {
				return LibUsbDevice.LibUsbDevices;
			}
		}

		#endregion
		
	}
}
